<?php

/**
 * @todo delete()
 */

class Rapide_Database_Record
{
	const TYPE_SELECT = 1;
	const TYPE_INSERT = 2;
	const TYPE_UPDATE = 3;

	private
		$_oDatabase,

		$_iType	 = 0,

		$_aSelect	 = array(),
		$_bDistinct = false,
		$_aInsert	 = array(),
		$_aUpdate	 = array(),
		$_aFrom		 = array(),
		$_aJoin		 = array(),
		$_aWhere		= array(),
		$_aGroup		= array(),
		$_aHaving	 = array(),
		$_aOrder		= array(),
		$_aLimit		= array();

	public function __call($sMethod, $aParameters)
	{
		if(isset($this->_oDatabase) && method_exists($this->_oDatabase, $sMethod))
		{
			return call_user_func_array(array($this->_oDatabase, $sMethod), $aParameters);
		}
		else
		{
			$sError = sprintf('Method %s does not exist', $sMethod);
			throw new Rapide_Database_Record_Exception($sError);
		}
	}

	// Return new, clear instance of Record

	public function instance()
	{
		return new self;
	}

	public function clear()
	{
		$this->_iType	 = 0;

		$this->_aSelect	 = array();
		$this->_bDistinct = false;
		$this->_aInsert	 = array();
		$this->_aUpdate	 = array();
		$this->_aFrom		 = array();
		$this->_aJoin		 = array();
		$this->_aWhere		= array();
		$this->_aGroup		= array();
		$this->_aHaving	 = array();
		$this->_aOrder		= array();
		$this->_aLimit		= array();
	}

	public function setDatabase(Rapide_Database_Interface $oDatabase)
	{
		$this->_oDatabase = $oDatabase;
	}

	public function select($sColumn = '*')
	{
		$this->_aSelect = func_get_args();
		$this->_iType = self::TYPE_SELECT;

		return $this;
	}

	public function distinct($bDistinct = true)
	{
		$this->_bDistinct = (bool)$bDistinct;
	}

	public function avg($sColumn)
	{
		$sColumn = 'AVG(' . $sColumn . ')';

		return $this->select($sColumn);
	}

	public function count($sColumn = '*')
	{
		$sColumn = 'COUNT(' . $sColumn . ')';

		return $this->select($sColumn);
	}

	public function min($sColumn)
	{
		$sColumn = 'MIN(' . $sColumn . ')';

		return $this->select($sColumn);
	}

	public function max($sColumn)
	{
		$sColumn = 'MAX(' . $sColumn . ')';

		return $this->select($sColumn);
	}

	public function std($sColumn)
	{
		$sColumn = 'STD(' . $sColumn . ')';

		return $this->select($sColumn);
	}

	public function sum($sColumn)
	{
		$sColumn = 'SUM(' . $sColumn . ')';

		return $this->select($sColumn);
	}

	public function from()
	{
		$this->_aFrom = func_get_args();

		return $this;
	}

	public function ljoin($sTable, $sOn)
	{
		$this->_join($sTable, $sOn);
		return $this;
	}

	public function rjoin($sTable, $sOn)
	{
		$this->_join($sTable, $sOn, 'RIGHT');
		return $this;
	}

	private function _join($sTable, $sOn, $sType = 'LEFT')
	{
		$sSQL = $sType . ' JOIN ' . $sTable . ' ON ' . $sOn;
		$this->_aJoin[] = $sSQL;
	}

	public function where()
	{
		$this->_where(func_get_args());
		return $this;
	}

	public function orWhere()
	{
		$this->_where(func_get_args(), 'OR');
		return $this;
	}

	private function _where(array $aConditions, $sType = 'AND')
	{
		foreach($aConditions as $sCondition)
			$this->_aWhere[] = array('condition' => $sCondition, 'type' => $sType);
	}

	public function like($sField, $sMatch)
	{
		return $this->_like($sField, $sMatch);
	}

	public function orLike($sField, $sMatch)
	{
		return $this->_like($sField, $sMatch, 'LIKE', 'OR');
	}

	public function notLike($sField, $sMatch)
	{
		return $this->_like($sField, $sMatch, 'NOT LIKE');
	}

	public function orNotLike($sField, $sMatch)
	{
		return $this->_like($sField, $sMatch, 'NOT LIKE', 'OR');
	}

	private function _like($sField, $sMatch, $sOperator = 'LIKE', $sType = 'AND')
	{
		$sCondition = $sField . ' ' . $sOperator . ' ' . $sMatch;

		if($sType == 'AND')
			return $this->where($sCondition);
		else
			return $this->orWhere($sCondition);
	}

	public function group()
	{
		$this->_aGroup = func_get_args();

		return $this;
	}

	public function having()
	{
		$this->_having(func_get_args());
		return $this;
	}

	public function orHaving()
	{
		$this->_having(func_get_args(), 'OR');
		return $this;
	}

	private function _having(array $aConditions, $sType = 'AND')
	{
		foreach($aConditions as $sCondition)
			$this->_aHaving[] = array('condition' => $sCondition, 'type' => $sType);
	}

	public function order()
	{
		$this->_aOrder = func_get_args();

		return $this;
	}

	public function limit($iStart, $iEnd)
	{
		$this->_aLimit['start'] = (int)$iStart;
		$this->_aLimit['end']	 = (int)$iEnd;

		return $this;
	}

	public function insert($sTableName, array $aInsert)
	{
		$this->_aInsert['table'] = $sTableName;

		if(count($aInsert))
		{
			$this->_aInsert['data'] = $aInsert;
			$this->_iType = self::TYPE_INSERT;
		}

		return $this;
	}

	public function update($sTableName, array $aUpdate)
	{
		$this->_aUpdate['table'] = $sTableName;

		if(count($aUpdate))
		{
			$this->_aUpdate['data'] = $aUpdate;
			$this->_iType = self::TYPE_UPDATE;
		}

		return $this;
	}

	public function exec($sSQL = null)
	{
		if(empty($sSQL))
			$sSQL = $this->sql();

		$this->_oDatabase->exec($sSQL);
		$this->clear();

		return $this;
	}

	public function sql()
	{
		switch($this->_iType)
		{
			case self::TYPE_SELECT : return $this->_createSelectQuery(); break;
			case self::TYPE_INSERT : return $this->_createInsertQuery(); break;
			case self::TYPE_UPDATE : return $this->_createUpdateQuery(); break;

			default:
				$sError = 'There was not query';
				throw new Rapide_Database_Record_Exception($sError);
		}
	}

	private function _createSelectQuery()
	{
		$sSQL = 'SELECT ' . ($this->_bDistinct ? 'DISTINCT ' : '') . implode(', ', $this->_aSelect);

		if(count($this->_aFrom))
		{
			$sSQL .= ' FROM ' . implode(', ', $this->_aFrom);
		}

		if(count($this->_aJoin))
		{
			$sSQL .= ' ' . implode(' ', $this->_aJoin);
		}

		if(count($this->_aWhere))
		{
			$sSQL .= ' WHERE ' . $this->_createConditions($this->_aWhere);
		}

		if(count($this->_aGroup))
		{
			$sSQL .= ' GROUP BY ' . implode(', ', $this->_aGroup);
		}

		if(count($this->_aHaving))
		{
			$sSQL .= ' HAVING ' . $this->_createConditions($this->_aHaving);
		}

		if(count($this->_aOrder))
		{
			$sSQL .= ' ORDER BY ' . implode(', ', $this->_aOrder);
		}

		if(count($this->_aLimit))
		{
			$sSQL .= ' LIMIT ' . $this->_aLimit['start'] . ', ' . $this->_aLimit['end'];
		}

		return $sSQL;
	}

	private function _createInsertQuery()
	{
		$sSQLFields = implode(', ', array_keys($this->_aInsert['data']));

		$sSQLValues = implode(', ', array_values($this->_aInsert['data']));

		return 'INSERT INTO ' . $this->_aInsert['table'] . ' (' . $sSQLFields . ') VALUES (' . $sSQLValues . ')';
	}

	private function _createUpdateQuery()
	{
		$aSQLValues = array();

		foreach($this->_aUpdate['data'] as $sField => $mValue)
			$aSQLValues[] = $sField . ' = ' . $mValue;

		$sSQLValues = implode(', ', $aSQLValues);

		$sSQL = 'UPDATE ' . $this->_aUpdate['table'] . ' SET ' . $sSQLValues;

		if(count($this->_aWhere))
		{
			$sSQL .= ' WHERE ' . $this->_createConditions($this->_aWhere);
		}

		if(count($this->_aOrder))
		{
			$sSQL .= ' ORDER BY ' . implode(', ', $this->_aOrder);
		}

		if(count($this->_aLimit))
		{
			$sSQL .= ' LIMIT ' . $this->_aLimit['start'] . ', ' . $this->_aLimit['end'];
		}

		return $sSQL;
	}

	private function _createConditions(array $aConditions)
	{
		$sSQL = '';

		for($i = 0; $i < count($aConditions); $i++)
		{
			if($i > 0)
				$sSQL .= ' ' . $aConditions[$i]['type'] . ' ';

			$sSQL .= $aConditions[$i]['condition'];
		}

		return $sSQL;
	}
}

?>